﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RaytracerLib.MathLib.Polynomials;
using System.Linq.Expressions;
using TomanuExtensions;
using System.Reflection;

namespace RaytracerLib.MathLib.PolynomialFunction
{
    public static class TreeCompiler
    {
        public const string DIR_PARAM = "a_dir";
        public const string START_PARAM = "a_start";
        public const string POINT_PARAM = "a_point";
        private static Dictionary<string, string> s_paramters_map;

        static TreeCompiler()
        {
            s_paramters_map = new Dictionary<string, string>();

            s_paramters_map[VariableSymbol.DIR + "." + VariableSymbol.X] = 
                DIR_PARAM + "." + VariableSymbol.X.ToUpper();
            s_paramters_map[VariableSymbol.DIR + "." + VariableSymbol.Y] = 
                DIR_PARAM + "." + VariableSymbol.Y.ToUpper();
            s_paramters_map[VariableSymbol.DIR + "." + VariableSymbol.Z] = 
                DIR_PARAM + "." + VariableSymbol.Z.ToUpper();

            s_paramters_map[VariableSymbol.START + "." + VariableSymbol.X] = 
                START_PARAM + "." + VariableSymbol.X.ToUpper();
            s_paramters_map[VariableSymbol.START + "." + VariableSymbol.Y] = 
                START_PARAM + "." + VariableSymbol.Y.ToUpper();
            s_paramters_map[VariableSymbol.START + "." + VariableSymbol.Z] = 
                START_PARAM + "." + VariableSymbol.Z.ToUpper();

            s_paramters_map[VariableSymbol.X] = 
                POINT_PARAM + "." + VariableSymbol.X.ToUpper();
            s_paramters_map[VariableSymbol.Y] = 
                POINT_PARAM + "." + VariableSymbol.Y.ToUpper();
            s_paramters_map[VariableSymbol.Z] = 
                POINT_PARAM + "." + VariableSymbol.Z.ToUpper();

            s_paramters_map[VariableSymbol.DIST] =  
                VariableSymbol.DIST;
        }

        public static Func<Vector3, double> CompilePolynomialFunction(Element a_root)
        {
            a_root = a_root.CloneTree();

            TreeTransforms.RenameVariables(a_root, s_paramters_map);
            a_root = TreeTransforms.UnpackVariables(a_root);

            var point_param = Expression.Parameter(typeof(Vector3), POINT_PARAM);

            Expression expression = BuildPolynomialExporession(a_root, point_param);

            return Expression.Lambda<Func<Vector3, double>>(expression, point_param).Compile();
        }

        public static Func<Vector3, Vector3> CompilePolynomialGradient(Element a_root)
        {
            a_root = a_root.CloneTree();

            var point_param = Expression.Parameter(typeof(Vector3), POINT_PARAM);

            Func<string, Expression> prepare = (diff) =>
            {
                Element delta = TreeTransforms.Diffrentiate(a_root, diff);
                delta = TreeTransforms.Simplify(delta);
                TreeTransforms.RenameVariables(delta, s_paramters_map);
                delta = TreeTransforms.UnpackVariables(delta);
                return BuildPolynomialExporession(delta, point_param);
            };

            Expression dx = prepare(VariableSymbol.X);
            Expression dy = prepare(VariableSymbol.Y);
            Expression dz = prepare(VariableSymbol.Z);

            ConstructorInfo ci = typeof(Vector3).GetConstructor(
                typeof(Double), typeof(Double), typeof(Double));
            Expression grd = Expression.New(ci, dx, dy, dz);

            return Expression.Lambda<Func<Vector3, Vector3>>(grd, point_param).Compile();
        }

        public static Func<Vector3, Vector3, Polynomial> CompileIntersectionPolynomial(Element a_root)
        {
            a_root = a_root.CloneTree();
            a_root = TreeTransforms.Expand(a_root, true, false);
            a_root = TreeTransforms.PlugLineEquation(a_root, VariableSymbol.START, 
                VariableSymbol.DIR, VariableSymbol.DIST);
            a_root = TreeTransforms.Expand(a_root, false, false);

            List<Element> coeffs1 = GroupByDistanceExponent(a_root);

            for (int i = 0; i < coeffs1.Count; i++)
            {
                coeffs1[i] = TreeTransforms.Simplify(coeffs1[i], new[] { VariableSymbol.START, VariableSymbol.DIR });
                TreeTransforms.RenameVariables(coeffs1[i], s_paramters_map);
                coeffs1[i] = TreeTransforms.UnpackVariables(coeffs1[i]);
            }

            ParameterExpression[] parameters = new[] 
            { 
                Expression.Parameter(typeof(Vector3), START_PARAM), 
                Expression.Parameter(typeof(Vector3), DIR_PARAM)
            };

            Expression[] coeffs2 = coeffs1.Select(el => BuildPolynomialExporession(el, parameters)).ToArray();

            Type[] types = Enumerable.Repeat<Type>(typeof(Double), coeffs2.Length).ToArray();
            ConstructorInfo ci = typeof(Polynomial).GetConstructor(types);

            Expression poly;

            if (ci != null)
                poly = Expression.New(ci, coeffs2);
            else
            {
                ci = typeof(Polynomial).GetConstructor(typeof(Double[]));
                poly = Expression.New(ci, Expression.NewArrayInit(typeof(Double), coeffs2));
            }

            return Expression.Lambda<Func<Vector3, Vector3, Polynomial>>(poly, parameters).Compile();
        }

        private static List<Element> GroupByDistanceExponent(Element a_root)
        {
            List<Element> add_groups = new List<Element>();

            foreach (var el in a_root.AllElements.OfType<AddOperator>())
            {
                if (!(el.Left is AddOperator))
                    add_groups.Add(el.Left.CloneTree());
                if (!(el.Right is AddOperator))
                    add_groups.Add(el.Right.CloneTree());
            }

            if (add_groups.Count == 0)
                add_groups.Add(a_root.CloneTree());

            foreach (var gr in add_groups)
            {
                if (!gr.AllChildren.OfType<VariableSymbol>().Select(el => el.Name).Unique())
                    throw new InvalidOperationException();
            }

            List<Element> result = new List<Element>();

            foreach (var el in a_root.AllElements.OfType<VariableSymbol>())
            {
                if (el.Name.Length == 0)
                    continue;

                if (el.Exponent <= 0)
                    throw new InvalidOperationException();
                if (el.Exponent.Fraction() != 0)
                    throw new InvalidOperationException();
            }

            Func<Element, int> t_exp = (el) =>
            {
                var vr_t = el.AllElements.OfType<VariableSymbol>().Where(
                    v => v.Name == VariableSymbol.DIST).FirstOrDefault();

                if (vr_t == null)
                    return 0;

                return (int)vr_t.Exponent;
            };

            Func<List<Element>, Element> PrepareAddTree = (list) =>
            {
                Element r = list.RemoveLast();

                while (list.Any())
                    r = new AddOperator(r, list.RemoveLast());

                return r;
            };

            Action<Element, List<Element>, int> PlaceInResult = (element, list, index) =>
            {
                while (list.Count <= index)
                    list.Add(new NumberSymbol(0));
                list[index] = element;
            };

            while (add_groups.Count > 0)
            {
                int coef_index = t_exp(add_groups.First());

                var add_groups_same_exp = add_groups.Where(el => t_exp(el) == coef_index).ToList();
                add_groups.RemoveRange(add_groups_same_exp);

                for (int i = 0; i < add_groups_same_exp.Count; i++)
                {
                    Element add_group = add_groups_same_exp[i];
                    VariableSymbol t_vec = add_group.AllElements.OfType<VariableSymbol>().
                        FirstOrDefault(v => v.Name == VariableSymbol.DIST);

                    if (t_vec == null)
                        continue;

                    if (t_vec == add_group)
                        add_group = TreeOperations.ReplaceTree(add_group, t_vec, new NumberSymbol(1));
                    else
                        add_group = TreeOperations.RemoveSymbol(add_group,   t_vec);

                    add_groups_same_exp[i] = add_group;
                }

                PlaceInResult(PrepareAddTree(add_groups_same_exp), result, coef_index);
            }

            return result;
        }

        private static Expression BuildPolynomialExporession(Element a_element, params ParameterExpression[] a_params)
        {
            if (a_element is MinusOperator)
            {
                return Expression.Negate(BuildPolynomialExporession(a_element.Children[0], a_params));
            }
            else if (a_element is AddOperator)
            {
                return Expression.Add(BuildPolynomialExporession(a_element.Children[0], a_params),
                    BuildPolynomialExporession(a_element.Children[1], a_params));

            }
            else if (a_element is SubOperator)
            {
                return Expression.Subtract(BuildPolynomialExporession(a_element.Children[0], a_params),
                    BuildPolynomialExporession(a_element.Children[1], a_params));

            }
            else if (a_element is MulOperator)
            {
                return Expression.Multiply(BuildPolynomialExporession(a_element.Children[0], a_params),
                    BuildPolynomialExporession(a_element.Children[1], a_params));
            }
            else if (a_element is PowOperator)
            {
                return Expression.Power(BuildPolynomialExporession(a_element.Children[0], a_params),
                    BuildPolynomialExporession(a_element.Children[1], a_params));
            }
            else if (a_element is VariableSymbol)
            {
                VariableSymbol vr = a_element as VariableSymbol;

                string[] name = vr.Name.Split(".").ToArray();

                if (name.Length == 2)
                {
                    ParameterExpression param = a_params.First(p => p.Name == name[0]);
                    return Expression.Field(param, name[1]);
                }
                else
                    return a_params.First(p => p.Name == name[0]);
            }
            else if (a_element is NumberSymbol)
            {
                return Expression.Constant((a_element as NumberSymbol).Value);
            }
            else
                throw new NotImplementedException();
        }
    }
}
